﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DataModels.Common;
using DataModels.Enumerations;
using DataModels.Models;
using GenericTools.Models;
using DataModels.Models.Context;
using DataModels.Models.DHIS;
using DHISInteroperability.DataTransfer;
using DHISInteroperability.Authentication;
using DHISInteroperability;

namespace GenericTools.OptionBPlus
{
    public abstract class FormValidator : IDisposable
    {
        #region Class Constants
        #region Private
        protected const int INVALID_MONTH = -1;
        protected const int INVALID_YEAR = -1;
        protected const int INVALID_REPORT_ID = -1;
        protected const int MINIMUM_VALID_YEAR = 2000;
        protected const int MAXIMUM_VALID_YEAR = 3000;

        private const int INVALID_REPORT_SECTION = -1;
        private const int MINIMUM_NUMBER_OF_DATA_SEGMENTS = 3;  // Age category; section; and at least one value
        private const int REPORT_DATA_AGE_CATEGORY_POSITION = 0;
        private const int REPORT_DATA_SECTION_CATEGORY_POSITIONS = 1;
        private const int REPORT_DATA_INDICATOR_VALUES_OFFSET = 2;

        #endregion

        #region Comparison
        protected static int COMPARISON_LABEL = 54;
        protected static int EQUALS_LABEL = 60;
        protected static int NOT_EQUALS_LABEL = 61;
        protected static int GREATER_THAN_LABEL = 62;
        protected static int GREATER_THAN_OR_EQUALS_LABEL = 63;
        protected static int LESS_THAN_LABEL = 64;
        protected static int LESS_THAN_OR_EQUALS_LABEL = 65;
        #endregion

        #region Public
        //public const string SETTINGS_KEY_MTUHA_REPORT_KEYWORDS = "MtuhaReportKeyword";

        public const string SETTINGS_KEY_MTUHA_REPORT_UNDER_20_KEYWORD = "MtuhaReportUnder20Keyword";
        public const string DEFAULT_MTUHA_REPORT_UNDER_20_KEYWORD = "C20";

        public const string SETTINGS_KEY_MTUHA_REPORT_ABOVE_20_KEYWORD = "MtuhaReportAbove20Keyword";
        public const string DEFAULT_MTUHA_REPORT_ABOVE_20_KEYWORD = "J20";

        public const string SETTINGS_KEY_MTUHA_REPORT_SECTION_KEYWORD = "MtuhaReportSectionPrefix";
        public const string DEFAULT_MTUHA_REPORT_SECTION_KEYWORD = "K";
        #endregion
        #endregion

        #region Class Variables
        //private int reportId;
        protected string facilityCode;
        protected int reportMonth;
        protected int reportYear;
        protected string message;
        protected string phoneNumberOrUsername;
        protected string password;
        protected string leftUnprocessedMessagePart;
        protected IQSMS_TestDBContext dbContext;
        protected bool savedFormRetrieved = false;

        private Dictionary<int, bool> reportSections;
        private List<string> includedReports;
        protected List<ReportDataSegment> resolvedReports;
        protected List<int> involvedClassicalReportIds;

        protected SMSReportingFormat format;
        protected ReportProcessingError error;
        protected ReportingMode mode;
        protected ReportingError reportingError = null;


        protected ApprovedSMSCell smsUser;
        protected User desktopUser;
        protected Facility userFacility;

        protected bool validated = false;

        private bool userRegistered = false;
        private bool userAuthenticated = false;

        private bool keywordValid = false;
        private bool keywordChecked = false;
        private string keyword = null;

        private bool facilityCodeValid = false;
        private bool facilityCodeChecked = false;

        private bool monthChecked = false;
        private bool yearChecked = false;
        private bool monthAndYearChecked = false;

        private bool includedReportsResolved = false;

        private List<MTUHAReportBase> _SavedForms = null;

        protected DataInputingMethod inputingMethod;
        protected bool saveInClassicFormat = true;
        #endregion

        #region Constructors
        public FormValidator(string message, string phoneNumberOrUsername, SMSReportingFormat format,
            ReportingMode mode, DataInputingMethod inputingMethod = DataInputingMethod.SMS, 
            string password = null, bool saveInClassicFormat = true)
        {
            if (inputingMethod == DataInputingMethod.SMS && password != null)
            {
                throw new InvalidOperationException("Password is not required if using SMS reporting");
            }

            this.format = format;
            this.message = message.Trim();
            this.phoneNumberOrUsername = phoneNumberOrUsername;
            this.password = password;
            this.mode = mode;
            this.inputingMethod = inputingMethod;
            this.saveInClassicFormat = saveInClassicFormat;

            this.leftUnprocessedMessagePart = this.message;

            this.reportSections = new Dictionary<int, bool>();
            this.includedReports = new List<string>();
            this.involvedClassicalReportIds = new List<int>();
            this.resolvedReports = new List<ReportDataSegment>();
            this._SavedForms = new List<MTUHAReportBase>();
            this.dbContext = new IQSMS_TestDBContext();

            this.error = ReportProcessingError.NO_ERROR;
            this.reportingError = ReportingError.GetReportingError(ReportProcessingError.NO_ERROR, phoneNumberOrUsername);

            //this.reportId = ANC_MTUHA_REPORT_ID;
        }
        #endregion

        #region Public Properties
        public bool HasError
        {
            get
            {
                if (!validated) Validate();
                return (this.error != ReportProcessingError.NO_ERROR);
            }
        }

        public bool ValidatedForErrors
        {
            get
            {
                return this.validated;
            }
        }

        public string Message
        {
            get
            {
                return this.message;
            }
        }

        public string PhoneNumber
        {
            get
            {
                return this.phoneNumberOrUsername;
            }
        }

        public IQSMS_TestDBContext DbContext
        {
            get 
            { 
                return dbContext; 
            }
        }

        public ReportingError ReportingError
        {
            get
            {
                return this.reportingError;
            }
        }

        public bool KeywordValid
        {
            get
            {
                if (!keywordChecked) IsKeywordValid();
                return keywordValid;
            }
        }

        public string Keyword
        {
            get
            {
                if (!keywordChecked) IsKeywordValid();
                return keyword;
            }
        }

        public bool FacilityCodeValid
        {
            get
            {
                if (!facilityCodeChecked) IsFacilityCodeValid();
                return facilityCodeValid;
            }
        }

        public string FacilityCode
        {
            get
            {
                if (!facilityCodeChecked) IsFacilityCodeValid();
                return this.facilityCode;
            }
        }

        public int ReportMonth
        {
            get
            {
                if (!monthAndYearChecked) 
                {
                    IsReportMonthAndYearValid();
                }
                return this.reportMonth;
            }
        }

        public int ReportYear
        {
            get
            {
                if (!monthAndYearChecked)
                {
                    IsReportMonthAndYearValid();
                }
                return this.reportYear;
            }
        }

        public List<ReportDataSegment> ResolvedReports
        {
            get
            {
                if (!includedReportsResolved) ResolveIncludedReports();
                return this.resolvedReports;
            }
        }

        /// <summary>
        /// Gives the list of saved reports. The list depends on the results of Save method.
        /// If Save method was never called in this instance of validator and return true, then
        /// the value return is null.
        /// Lastly the results are not in-memory data deduced by this instance internal storage.
        /// They fetched directly from the database after changes have been committed.
        /// </summary>
        //public List<MTUHAReportBase> SavedForms
        //{
        //    get
        //    {
        //        if (!savedFormRetrieved)
        //        {
        //            bool under20Found = false;
        //            bool above20Found = false;
        //            foreach (var g in this.ResolvedReports)
        //            {
        //                if (g.AgeCategory == MTUHAAgeCategory.UNDER_20_YEARS) under20Found = true;
        //                if (g.AgeCategory == MTUHAAgeCategory.ABOVE_20_YEARS) above20Found = true;
        //            }

        //            if (under20Found && above20Found)
        //            {
        //                _SavedForms = (from f in dbContext.ANCForms
        //                               where f.FacilityCode == this.facilityCode
        //                               && f.Year == this.reportYear
        //                               && f.Month == this.reportMonth
        //                               select f).ToList();
        //            }
        //            else
        //            {
        //                bool lessThan20 = default(bool);
        //                if (under20Found) lessThan20 = true;
        //                if (above20Found) lessThan20 = false;

        //                _SavedForms = (from f in dbContext.ANCForms
        //                               where f.FacilityCode == this.facilityCode
        //                               && f.LessThan20 == lessThan20
        //                               && f.Year == this.reportYear
        //                               && f.Month == this.reportMonth
        //                               select f).ToList();
        //            }                    
        //        }
        //        return _SavedForms;
        //    }
        //}

        #endregion

        public bool Validate()
        {
            bool valid = false;

            // 1. Check the keyword
            bool okToContinue = IsKeywordValid();
            if (okToContinue)
            {
                // 2. Is the user registered
                okToContinue = IsUserRegistered();
                if (okToContinue)
                {
                    // 3. Check the facility code
                    // 4. Is user allowed to send report using this facility code
                    okToContinue = IsFacilityCodeValid();
                    if (okToContinue)
                    {
                        // 5. Check the month
                        okToContinue = IsReportMonthValid();
                        if (okToContinue)
                        {
                            // 6. Check the year
                            okToContinue = IsReportYearValid();
                            if (okToContinue)
                            {
                                // 7. Are month and year making any sense
                                okToContinue = IsReportMonthAndYearValid();
                                if (okToContinue)
                                {
                                    ResolveIncludedReports();
                                    valid = (error == ReportProcessingError.NO_ERROR);
                                }
                            }
                        }
                    }
                }
            }

            validated = true;
            if (valid) IdentifyInvolvedClassicalReportIds();
            else this.resolvedReports.Clear();
            return valid;
        }

        protected abstract bool ValidateReportIndicators(ReportDataSegment reportDataSegment);

        public bool SaveInClassicalReportTemplates()
        {
            bool noErrorFound = true;
            string phoneNum = this.phoneNumberOrUsername;
            if (this.inputingMethod != DataInputingMethod.SMS)
            {
                using (var db = new IQSMS_TestDBContext())
                {
                    string accessLevel = (from a in db.AccessLevels
                                          where a.AccessID == this.desktopUser.AccessLevelID
                                          select a.AccessName).FirstOrDefault();

                    if (string.IsNullOrWhiteSpace(accessLevel)) accessLevel = "UNKNOWN LEVEL";
                    phoneNum = String.Format("{0} {1} [{2} Level - Data Capture]",
                        this.desktopUser.FirstName, this.desktopUser.LastName, accessLevel);
                }
            }

            string reportKeywords = GetClassicalReportKeywords();
            string[] keywords = reportKeywords.Split('|');

            foreach (var report in this.resolvedReports)
            {
                string keys = keywords[report.SectionNumber - 1];
                string[] ageBasedKeys = keys.Split(',');
                string under20Key = ageBasedKeys[0];
                string above20Key = ageBasedKeys[1];
                string targetKey = string.Empty;

                if (report.AgeCategory == MTUHAAgeCategory.UNDER_20_YEARS) targetKey = under20Key;
                if (report.AgeCategory == MTUHAAgeCategory.ABOVE_20_YEARS) targetKey = above20Key;

                // Creating something like. K1C20.04010100,1,2014,1...,
                StringBuilder sBuilder = new StringBuilder();
                sBuilder.Append(targetKey + ".");
                sBuilder.Append(this.FacilityCode + ",");
                sBuilder.Append(this.ReportMonth + ",");
                sBuilder.Append(this.ReportYear);

                foreach (var s in report.Indicators) sBuilder.Append("," + s);
                string reportingText = sBuilder.ToString();

                var operationStatus = Merging.SendReport(phoneNum, reportingText,
                    this.format, this.inputingMethod);
                if (operationStatus.Status == ReportProcessing.ReportProcessor.REPORT_ALREADY_SUBMITTED_LABEL)
                {
                    reportingText = String.Format("{0},RESUBMIT", reportingText);
                    operationStatus = Merging.SendReport(phoneNum, reportingText, this.format, this.inputingMethod);
                    if (operationStatus.Status != ReportProcessing.ReportProcessor.NO_ERROR_LABEL)
                    {
                        noErrorFound = false;
                        break;
                    }
                }
                else
                {
                    if (operationStatus.Status != ReportProcessing.ReportProcessor.NO_ERROR_LABEL)
                    {
                        noErrorFound = false;
                        break;
                    }
                }
            }
            return noErrorFound;
        }

        protected abstract string GetReportKeywords();

        /// <summary>
        /// Returns the list of all component reportIds for this MTUHA report.
        /// </summary>
        /// <returns>List of integer</returns>
        public List<int> GetInvolvedClassicalReportId()
        {
            return this.involvedClassicalReportIds;
        }

        protected abstract string GetClassicalReportKeywords();

        protected abstract bool Insert(ReportDataSegment report, ANCFormMetadata metadata);

        protected abstract bool Update(ReportDataSegment report, ANCFormMetadata metadata);

        public bool IsUserRegistered()
        {
            if (userAuthenticated) return userRegistered;
            // Is the user registered?
            userRegistered = false;
            
            switch (inputingMethod)
            {
                case DataInputingMethod.DESKTOP_DATA_CAPTURE:
                case DataInputingMethod.DESKTOP_BULK_DATA_IMPORT:
                case DataInputingMethod.DESKTOP_DATA_MERGING:
                case DataInputingMethod.MOBILE_BULK_DATA_IMPORT:
                case DataInputingMethod.MOBILE_DATA_CAPTURE:
                case DataInputingMethod.MOBILE_DATA_MERGING:
                case DataInputingMethod.WEB_BULK_DATA_IMPORT:
                case DataInputingMethod.WEB_DATA_CAPTURE:
                case DataInputingMethod.WEB_DATA_MERGING:
                    var uResults = from u in dbContext.Users
                                   where u.Password == password
                                   select u;

                    var lstUResults = uResults.ToList();
                    if (lstUResults.Count > 0)
                    {
                        this.desktopUser = lstUResults[0];
                        userRegistered = true;
                    }
                    break;

                case DataInputingMethod.SMS:
                default:
                    var results = from u in dbContext.ApprovedSMSCells
                          where u.CellNum == phoneNumberOrUsername
                          select u;

                    var lstResults = results.ToList();
                    if (lstResults.Count > 0)
                    {
                        smsUser = lstResults[0];
                        this.userFacility = (from f in dbContext.Facilities
                                             where f.FacCode == smsUser.FacCode
                                             select f).SingleOrDefault();
                        if (this.userFacility != null) userRegistered = true;
                    }
                    break;                    
            }

            if (!userRegistered)
            {
                error = ReportProcessingError.SENDER_NUMBER_IS_NOT_REGISTERED;
                this.reportingError = ReportingError.GetReportingError(error);
            }

            userAuthenticated = true;
            return userRegistered;            
        }

        /// <summary>
        /// Determines if the reporting text has the report keyword at the beginning.
        /// The keyword may be followed by period or comma
        /// </summary>
        /// <returns>True if the reporting text has the keyword otherwise false</returns>
        private bool IsKeywordValid()
        {
            if (keywordChecked) return keywordValid;

            string reportKeywords = GetReportKeywords();
            int periodIndex = message.IndexOf('.');
            if (periodIndex != -1)
            {
                string prefix = message.Substring(0, periodIndex);
                string[] possibleKeywords = reportKeywords.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);

                bool foundKeyword = false;
                foreach (var key in possibleKeywords)
                {
                    if (key.Equals(prefix, StringComparison.OrdinalIgnoreCase))
                    {
                        periodIndex++;
                        leftUnprocessedMessagePart = message.Substring(periodIndex);
                        keyword = key;
                        keywordValid = true;
                        foundKeyword = true;
                        break;
                    }
                }

                if (!foundKeyword)
                {
                    error = ReportProcessingError.REPORT_DOES_NOT_EXIST;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    keywordValid = false;
                }
            }
            // OK; the message does not have any period; now lets try the presence of comma
            else
            {
                if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                {
                    int commaIndex = message.IndexOf(',');
                    if (commaIndex != -1)
                    {
                        string prefix = message.Substring(0, commaIndex);
                        string[] possibleKeywords = reportKeywords.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);

                        bool foundKeyword = false;
                        foreach (var key in possibleKeywords)
                        {
                            if (key.Equals(prefix, StringComparison.OrdinalIgnoreCase))
                            {
                                commaIndex++;
                                leftUnprocessedMessagePart = message.Substring(commaIndex);
                                keyword = key;
                                keywordValid = true;
                                foundKeyword = true;
                                break;
                            }
                        }

                        if (!foundKeyword)
                        {
                            error = ReportProcessingError.REPORT_DOES_NOT_EXIST;
                            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                            keywordValid = false;
                        }
                    }
                    else
                    {
                        error = ReportProcessingError.REPORT_DOES_NOT_EXIST;
                        this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                        keywordValid = false;
                    }
                }
                else
                {
                    error = ReportProcessingError.REPORT_DOES_NOT_EXIST;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    keywordValid = false;
                }
            }

            keywordChecked = true;
            return keywordValid;
        }

        private bool IsFacilityCodeValid()
        {
            if (!userAuthenticated) IsUserRegistered();
            if (userRegistered)
            {
                if (!keywordChecked) IsKeywordValid();
                if (facilityCodeChecked) return facilityCodeValid;

                // TODO: Operation confirmation check should at the bottom of the block
                facilityCodeChecked = true;

                int periodIndex = leftUnprocessedMessagePart.IndexOf(',');
                if (periodIndex != -1)
                {
                    string codeText = leftUnprocessedMessagePart.Substring(0, periodIndex);
                    if (format ==  SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                    {
                        // In case someone used O instead of 0 in the facility code
                        codeText = codeText.ToLower();
                        codeText = codeText.Replace('o', '0');
                    }

                    // Is the facility code exists
                    var facility = (from f in dbContext.Facilities
                                    where f.FacCode == codeText
                                    select f).SingleOrDefault();

                    if (facility != null)
                    {
                        this.facilityCode = facility.FacCode;
                        // Ok; the facility exists; but are you registered in this facility.
                        if (inputingMethod == DataInputingMethod.SMS)
                        {
                            if (userFacility.FacCode == facility.FacCode)
                            {
                                periodIndex++;
                                leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(periodIndex);

                                facilityCodeChecked = true;
                                facilityCodeValid = true;
                                return facilityCodeValid;
                            }
                            else
                            {
                                error = ReportProcessingError.SENDER_NUMBER_IS_NOT_REGISTERED_FOR_SELECTED_FACILITY;
                                this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                                facilityCodeChecked = true;
                                facilityCodeValid = false;
                                return facilityCodeValid;
                            }
                        }
                        else
                        {
                            this.userFacility = (from f in dbContext.Facilities
                                                 where f.FacCode == this.facilityCode
                                                 select f).SingleOrDefault();

                            periodIndex++;
                            leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(periodIndex);

                            facilityCodeChecked = true;
                            facilityCodeValid = true;
                            return facilityCodeValid;
                        }
                    }
                    else
                    {
                        error = ReportProcessingError.INVALID_FACILITY;
                        this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                        facilityCodeChecked = true;
                        facilityCodeValid = false;
                        return facilityCodeValid;
                    }
                }
                else
                {
                    error = ReportProcessingError.INVALID_FACILITY;
                    facilityCodeChecked = true;
                    facilityCodeValid = false;
                    return facilityCodeValid;
                }

                //else
                //{
                //    if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                //    {
                //        int commaIndex = leftUnprocessedMessagePart.IndexOf(',');
                //        if (commaIndex != -1)
                //        {
                //            string codeText = leftUnprocessedMessagePart.Substring(0, commaIndex);
                //            if (format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                //            {
                //                // In case someone used O instead of 0 in the facility code
                //                codeText = codeText.ToLower();
                //                codeText = codeText.Replace('o', '0');
                //            }

                //            // Is the facility code exists
                //            var facility = (from f in dbContext.Facilities
                //                            where f.FacCode == codeText
                //                            select f).SingleOrDefault();

                //            if (facility != null)
                //            {
                //                this.facilityCode = facility.FacCode;
                //                // Ok; the facility exists; but are you registered in this facility.
                //                if (userFacility.FacCode == facility.FacCode)
                //                {
                //                    commaIndex++;
                //                    leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(commaIndex);
                //                    facilityCodeChecked = true;
                //                    facilityCodeValid = true;
                //                    return facilityCodeValid;
                //                }
                //                else
                //                {
                //                    error = ReportProcessingError.SENDER_NUMBER_IS_NOT_REGISTERED_FOR_SELECTED_FACILITY;
                //                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                //                    facilityCodeChecked = true;
                //                    facilityCodeValid = false;
                //                    return facilityCodeValid;
                //                }
                //            }
                //            else
                //            {
                //                error = ReportProcessingError.INVALID_FACILITY;
                //                this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                //                facilityCodeChecked = true;
                //                facilityCodeValid = false;
                //                return facilityCodeValid;
                //            }
                //        }
                //        else
                //        {
                //            error = ReportProcessingError.INVALID_FACILITY;
                //            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                //            facilityCodeChecked = true;
                //            facilityCodeValid = false;
                //            return facilityCodeValid;
                //        }
                //    }
                //    else
                //    {
                //        error = ReportProcessingError.INVALID_FACILITY;
                //        facilityCodeChecked = true;
                //        facilityCodeValid = false;
                //        return facilityCodeValid;
                //    }
                //}
            }
            else
            {
                return facilityCodeValid;            
            }
        }

        private bool IsReportMonthValid()
        {
            if (!userAuthenticated) IsUserRegistered();
            if (userRegistered)
            {
                if (!facilityCodeChecked) IsFacilityCodeValid();
                if (this.error == ReportProcessingError.NO_ERROR)
                {
                    monthChecked = true;

                    int commaIndex = leftUnprocessedMessagePart.IndexOf(',');
                    if (commaIndex != -1)
                    {
                        string monthText = leftUnprocessedMessagePart.Substring(0, commaIndex);
                        int month = GetMonthFromText(monthText);
                        if (month != INVALID_MONTH)
                        {
                            this.reportMonth = month;
                            commaIndex++;
                            leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(commaIndex);
                            return true;
                        }
                    }

                    if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                    {
                        int periodIndex = leftUnprocessedMessagePart.IndexOf('.');
                        if (periodIndex != -1)
                        {
                            string monthText = leftUnprocessedMessagePart.Substring(0, periodIndex);
                            int month = GetMonthFromText(monthText);
                            if (month != INVALID_MONTH)
                            {
                                this.reportMonth = month;
                                periodIndex++;
                                leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(periodIndex);
                                return true;
                            }
                        }
                    }

                    error = ReportProcessingError.INVALID_PERIOD;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    return false;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private int GetMonthFromText(string monthText)
        {
            int month = INVALID_MONTH;
            // If the text is plain number
            string possibleMonthText = monthText;
            if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
            {
                possibleMonthText = possibleMonthText.ToLower();
                possibleMonthText = possibleMonthText.Replace('o','0');
            }

            bool okToContinue = int.TryParse(possibleMonthText, out month);
            if (okToContinue)
            {
                if (1 <= month && month <= 12)
                {
                    return month;
                }
            }
            else
            {
                // If the text contain the text in english
                string[] months = 
                { 
                    "january", "february", "march", "april", 
                    "may", "june", "julay", "august", 
                    "september", "october", "november", "december"
                };

                monthText = monthText.ToLower();
                for (int i = 0; i < months.Length; i++)
                {
                    if (months[i].Contains(monthText))
                    {
                        month = i + 1;
                        return month;
                    }
                }
            }

            return INVALID_MONTH;
        }

        private int GetYearFromText(string yearText)
        {
            int year = INVALID_YEAR;
            // If the text is plain number
            if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
            {
                yearText = yearText.ToLower();
                yearText = yearText.Replace('o', '0');
            }

            bool okToContinue = int.TryParse(yearText, out year);
            if (okToContinue)
            {
                if (MINIMUM_VALID_YEAR < year && year < MAXIMUM_VALID_YEAR)
                {
                    return year;
                }
            }

            return INVALID_YEAR;
        }

        private bool IsReportYearValid()
        {
            if (!userAuthenticated) IsUserRegistered();
            if (userRegistered)
            {
                if (!facilityCodeChecked) IsFacilityCodeValid();
                if (this.error == ReportProcessingError.NO_ERROR)
                {
                    yearChecked = true;

                    int commaIndex = leftUnprocessedMessagePart.IndexOf(',');
                    if (commaIndex != -1)
                    {
                        string yearText = leftUnprocessedMessagePart.Substring(0, commaIndex);
                        int year = GetYearFromText(yearText);
                        if (year != INVALID_YEAR)
                        {
                            this.reportYear = year;
                            commaIndex++;
                            leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(commaIndex);
                            return true;
                        }
                    }

                    if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                    {
                        int periodIndex = leftUnprocessedMessagePart.IndexOf('.');
                        if (periodIndex != -1)
                        {
                            string yearText = leftUnprocessedMessagePart.Substring(0, periodIndex);
                            int year = GetMonthFromText(yearText);
                            if (year != INVALID_YEAR)
                            {
                                this.reportYear = year;
                                periodIndex++;
                                leftUnprocessedMessagePart = leftUnprocessedMessagePart.Substring(periodIndex);
                                return true;
                            }
                        }
                    }

                    error = ReportProcessingError.INVALID_YEAR;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    return false;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }

        private bool IsReportMonthAndYearValid()
        {
            if (!monthChecked) IsReportMonthValid();
            if (!yearChecked) IsReportYearValid();

            if (this.error == ReportProcessingError.NO_ERROR)
            {
                monthAndYearChecked = true;
                bool isValid = false;

                int maxDelay = AppConfiguration.MaximumReportDelay;

                DateTime someYearsBack = DateTime.Now.AddYears(-maxDelay);
                DateTime currentTime = DateTime.Now;
                DateTime reportTime = DateTime.Parse(string.Format("{0}-{1}-1", this.reportYear, this.reportMonth));

                if (reportTime <= currentTime)
                {
                    if (reportTime >= someYearsBack)
                    {
                        if ((mode & ReportingMode.ALLOW_SAME_MONTH_REPORTING)
                            != ReportingMode.ALLOW_SAME_MONTH_REPORTING)
                        {
                            var now = DateTime.Now;
                            int currentMonth = now.Month;
                            int currentYear = now.Year;
                            if (currentMonth == reportTime.Month && currentYear == reportTime.Year)
                            {
                                error = ReportProcessingError.CURRENT_MONTH_REPORTING_NOT_ALLOWED;
                                reportingError = ReportingError.GetReportingError(error);
                                isValid = false;
                                return isValid;
                            }
                        }

                        isValid = true;
                        return isValid;
                    }
                    else
                    {
                        string errorText = Utilities.GetTranslationText(ReportingError.REPORT_IS_VERY_OLD_LABEL);
                        errorText = string.Format(errorText, AppConfiguration.MaximumReportDelay);
                        
                        error = ReportProcessingError.REPORT_IS_VERY_OLD;
                        reportingError = ReportingError.GetReportingError(error);
                        reportingError.ErrorText = errorText; // slightly changing the reporting text
                        isValid = false;
                        return isValid;
                    }
                }
                else
                {
                    error = ReportProcessingError.REPORT_HAS_FUTURE_RECORDS;
                    reportingError = ReportingError.GetReportingError(error);
                    isValid = false;
                    return isValid;
                }
            }
            else
            {
                return false;
            }
        }

        private static MTUHAAgeCategory GetAgeCategoryFromText(string ageCategoryText)
        {
            string under20Keyword = GenericTools.Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_UNDER_20_KEYWORD);
            if (string.IsNullOrEmpty(under20Keyword))
            {
                GenericTools.Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_UNDER_20_KEYWORD, DEFAULT_MTUHA_REPORT_UNDER_20_KEYWORD);
                under20Keyword = DEFAULT_MTUHA_REPORT_UNDER_20_KEYWORD;
            }

            string above20Keyword = GenericTools.Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_ABOVE_20_KEYWORD);
            if (string.IsNullOrEmpty(above20Keyword))
            {
                GenericTools.Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_ABOVE_20_KEYWORD, DEFAULT_MTUHA_REPORT_ABOVE_20_KEYWORD);
                above20Keyword = DEFAULT_MTUHA_REPORT_ABOVE_20_KEYWORD;
            }

            if (ageCategoryText.Equals(under20Keyword, StringComparison.OrdinalIgnoreCase))
            {
                return MTUHAAgeCategory.UNDER_20_YEARS;
            }

            if (ageCategoryText.Equals(above20Keyword, StringComparison.OrdinalIgnoreCase))
            {
                return MTUHAAgeCategory.ABOVE_20_YEARS;
            }

            return MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY;
        }

        private static int GetReportSectionFromText(string sectionText)
        {
            string sectionPrefix = GenericTools.Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_SECTION_KEYWORD);
            if (string.IsNullOrEmpty(sectionPrefix))
            {
                GenericTools.Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_SECTION_KEYWORD, DEFAULT_MTUHA_REPORT_SECTION_KEYWORD);
                sectionPrefix = DEFAULT_MTUHA_REPORT_SECTION_KEYWORD;
            }

            string sectionNumber = Regex.Replace(sectionText, sectionPrefix, string.Empty, RegexOptions.IgnoreCase);
            int number = INVALID_REPORT_SECTION;
            bool okToContinue = int.TryParse(sectionNumber, out number);
            if (okToContinue)
            {
                return number;
            }

            return INVALID_REPORT_SECTION;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="section"></param>
        /// <param name="reportId"></param>
        /// <param name="parsingText"></param>
        /// <returns></returns>
        /// <remarks>
        /// parsingText is the text that represents a single section of report.
        /// Other section should be determined before calling this method.
        /// </remarks>
        private List<int> GetIndicatorSectionValue(int section, int reportId, string parsingText)
        {
            // TODO: Deal with resubmission, but for now we'll just ignore them altogether
            #region Dealing with resubmissions
            parsingText = parsingText.ToLower();
            parsingText = parsingText.Replace("re", string.Empty);
            parsingText = parsingText.Replace("resubmit", string.Empty);
            parsingText = parsingText.Replace("rekebisha", string.Empty);
            parsingText = parsingText.Replace("marekebisho", string.Empty);
            parsingText = parsingText.Replace("sahihisha", string.Empty);
            parsingText = parsingText.Replace("sahihi", string.Empty);
            parsingText = parsingText.Replace("rudia", string.Empty);
            parsingText = parsingText.Replace("tumatena", string.Empty);
            parsingText = parsingText.Replace("tumaupya", string.Empty);
            parsingText = parsingText.Replace("badili", string.Empty);
            #endregion

            var indicators = new List<int>();
            string[] components = parsingText.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            int value;
            foreach (string component in components)
            {
                bool okToContinue = int.TryParse(component, out value);
                if (okToContinue) indicators.Add(value);
                else
                {
                    error = ReportProcessingError.NON_INTEGER_VALUE;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    return null;
                }
            }
            return indicators;

            //MTUHA_ReportSections reportSection = dbContext
            //    .MTUHA_ReportSections
            //    .Where(s => s.SectionNumber == section && s.MTUHAReportId == reportId)
            //    .SingleOrDefault();

            //if (reportSection != null)
            //{
            //    int targetSectionCount = dbContext
            //        .MTUHA_ReportSectionDetails
            //        .Where(s => s.SectionId == reportSection.SectionId)
            //        .Count();

            //    if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
            //    {
            //        parsingText = parsingText.Replace('.', ',');
            //        if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
            //        {
            //            parsingText = parsingText.ToLower();
            //            parsingText = parsingText.Replace('o', '0');
            //        }
            //    }

            //    string[] components = parsingText.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
            //    if (components.Length == targetSectionCount)
            //    {
            //        // The number of indicators matches; this is so great.
            //        int value;
            //        foreach (string component in components)
            //        {
            //            bool okToContinue = int.TryParse(component, out value);
            //            if (okToContinue) indicators.Add(value);
            //            else
            //            {
            //                error = ReportProcessingError.NON_INTEGER_VALUE;
            //                this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
            //                return null;
            //            }
            //        }
            //        return indicators;
            //    }
            //    else
            //    {
            //        if (components.Length > targetSectionCount)
            //        {
            //            // This is wrong too many parameters
            //            error = ReportProcessingError.MANY_REPORT_PARAMETERS;
            //            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
            //            return null;
            //        }
            //        else
            //        {
            //            // This is wrong very few parameters
            //            error = ReportProcessingError.FEW_REPORT_PARAMETERS;
            //            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
            //            return null;
            //        }
            //    }
            //}
            //else
            //{
            //    // this report does not continue well after the report year
            //    // so we are going to say this to the user.
            //    error = ReportProcessingError.INVALID_REPORT_FORMAT_AFTER_YEAR;
            //    reportingError = ReportingError.GetReportingError(error);
            //    return null;
            //}
        }

        private void DetermineAgeCategoryAndReportSection(string report, out int sectionNumber, out MTUHAAgeCategory ageCategory)
        {
            sectionNumber = INVALID_REPORT_SECTION;
            ageCategory = MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY;

            bool ageFoundInFirstPosition = false;
            bool ageFoundInSecondPosition = false;
            string[] parts = report.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);            

            string ageCategoryText = parts[0];
            var category = GetAgeCategoryFromText(ageCategoryText);
            if (category == MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY)
            {
                // Age category is not in the first position,
                // Trying in the second position
                ageCategoryText = parts[1];
                category = GetAgeCategoryFromText(ageCategoryText);
                if (category != MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY) 
                {
                    ageFoundInSecondPosition = true;
                    ageCategory = category;
                }
            }
            else 
            {
                ageFoundInFirstPosition = true;
                ageCategory = category;
            }

            if (ageFoundInFirstPosition)
            {
                string sectionText = parts[1];
                sectionNumber = GetReportSectionFromText(sectionText);
            }

            if (ageFoundInSecondPosition)
            {
                string sectionText = parts[0];
                sectionNumber = GetReportSectionFromText(sectionText);
            }

            // May be the age is the only one missing, let try to look for section number
            sectionNumber = GetReportSectionFromText(parts[0]);
            if (sectionNumber == INVALID_REPORT_SECTION) sectionNumber = GetReportSectionFromText(parts[1]);
        }

        private void DetermineIncludedReports()
        {
            if (!monthAndYearChecked) IsReportMonthAndYearValid();

            if (this.error == ReportProcessingError.NO_ERROR)
            {
                #region Getting Everything we need
                this.includedReports.Clear();
                string under20Keyword = Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_UNDER_20_KEYWORD);
                if (string.IsNullOrEmpty(under20Keyword))
                {
                    Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_UNDER_20_KEYWORD, DEFAULT_MTUHA_REPORT_UNDER_20_KEYWORD);
                    under20Keyword = DEFAULT_MTUHA_REPORT_UNDER_20_KEYWORD;
                }

                string above20Keyword = Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_ABOVE_20_KEYWORD);
                if (string.IsNullOrEmpty(above20Keyword))
                {
                    Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_ABOVE_20_KEYWORD, DEFAULT_MTUHA_REPORT_ABOVE_20_KEYWORD);
                    above20Keyword = DEFAULT_MTUHA_REPORT_ABOVE_20_KEYWORD;
                }

                string sectionKeyword = Utilities.GetSetting(SETTINGS_KEY_MTUHA_REPORT_SECTION_KEYWORD);
                if (string.IsNullOrEmpty(sectionKeyword))
                {
                    Utilities.SetSetting(SETTINGS_KEY_MTUHA_REPORT_SECTION_KEYWORD, DEFAULT_MTUHA_REPORT_SECTION_KEYWORD);
                    sectionKeyword = DEFAULT_MTUHA_REPORT_SECTION_KEYWORD;
                }

                string text = leftUnprocessedMessagePart;
                if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                {
                    text = text.Replace('o', '0');
                    text = text.Replace('O', '0');
                }
                #endregion

                if (text.StartsWith(under20Keyword, StringComparison.OrdinalIgnoreCase) 
                    || text.StartsWith(above20Keyword, StringComparison.OrdinalIgnoreCase)
                    /*|| text.StartsWith(sectionKeyword, StringComparison.OrdinalIgnoreCase)*/)
                {
                    //int under20StartIndex = under20Keyword.Length;
                    //int above20StartIndex = above20Keyword.Length;
                    //int minimumTextLength = Math.Min(under20Keyword.Length, above20Keyword.Length);

                    int previousUnder20Index = text.IndexOf(under20Keyword, StringComparison.OrdinalIgnoreCase);
                    int previousAbove20Index = text.IndexOf(above20Keyword, StringComparison.OrdinalIgnoreCase);
                    //int previousSectionIndex = text.IndexOf(sectionKeyword, StringComparison.OrdinalIgnoreCase);
                    //bool foundReport = previousUnder20Index != -1 || previousAbove20Index != -1 || previousSectionIndex != -1;
                    do
                    {
                        if (string.IsNullOrWhiteSpace(text))
                        {
                            // Mh!!! the text is empty or atleast contains nothing valuable...
                            break;
                        }

                        int startUnder20Index = text.IndexOf(under20Keyword, StringComparison.OrdinalIgnoreCase);
                        int startAbove20Index = text.IndexOf(above20Keyword, StringComparison.OrdinalIgnoreCase);
                        //int startSectionIndex = text.IndexOf(sectionKeyword, StringComparison.OrdinalIgnoreCase);
                        if (startUnder20Index == -1 && startAbove20Index == -1 /*&& startSectionIndex == -1*/)
                        {
                            // The message format is not valid; it is missing age category
                            error = ReportProcessingError.INVALID_AGE_CATEGORY_OR_REPORT_SECTION;
                            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                            break;
                        }
                        else
                        {
                            // We have found a report
                            int lookupIndex = 0;

                            if (startUnder20Index == -1) lookupIndex = startAbove20Index;
                            if (startAbove20Index == -1) lookupIndex = startUnder20Index;
                            if (startUnder20Index != -1 && startAbove20Index != -1 /*&& startSectionIndex != -1*/) 
                            {
                                lookupIndex = Math.Min(startUnder20Index, startAbove20Index);
                                //lookupIndex = FindMinimumIndex(false, startUnder20Index, startUnder20Index, startSectionIndex);
                                //lookupIndex++;
                            }
                            //else
                            //{
                            //    // The start of this report segment is report section
                            //    // and we are going to take a very different path, this was
                            //    // not originally planned, just patched code
                            //    lookupIndex++;
                            //    int under20Index = text.IndexOf(under20Keyword, lookupIndex, StringComparison.OrdinalIgnoreCase);
                            //    int above20Index = text.IndexOf(above20Keyword, lookupIndex, StringComparison.OrdinalIgnoreCase);
                            //    int sectionIndex = text.IndexOf(sectionKeyword, lookupIndex, StringComparison.OrdinalIgnoreCase);
                            //}

                            lookupIndex++;
                            int endUnder20Index = text.IndexOf(under20Keyword, lookupIndex, StringComparison.OrdinalIgnoreCase);
                            int endAbove20Index = text.IndexOf(above20Keyword, lookupIndex, StringComparison.OrdinalIgnoreCase);
                            //int endSectionIndex = text.IndexOf(sectionKeyword, lookupIndex, StringComparison.OrdinalIgnoreCase);

                            int index = 0;
                            if (endUnder20Index == -1) index = endAbove20Index;
                            if (endAbove20Index == -1) index = endUnder20Index;
                            if (endUnder20Index != -1 && endAbove20Index != -1 /*&& endSectionIndex != -1*/)
                            {
                                index = Math.Min(endUnder20Index, endAbove20Index);
                                //index = FindMinimumIndex(true, endUnder20Index, endAbove20Index, endSectionIndex);
                            }

                            if (index == -1) index = text.Length;
                            string report = text.Substring(0, index);
                            this.includedReports.Add(report);

                            text = text.Substring(index);
                        }
                    } while (true);
                }
                else
                {
                    // The report segment is missing age category keyword
                    error = ReportProcessingError.INVALID_AGE_CATEGORY_OR_REPORT_SECTION;
                    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                    return;
                }
            }
        }

        /// <summary>
        /// Return the minimum valid index.
        /// Values less than 0 are ignored. 
        /// This means if all indices values are -1, the function returns zero instead.
        /// </summary>
        /// <param name="indices"></param>
        /// <returns></returns>
        private static int FindMinimumIndex(bool allowInvalidIndex, params int[] indices)
        {
            int minimumIndex = indices.Length > 0 ? indices[0] : int.MinValue;
            foreach (var i in indices) 
            {
                if (!allowInvalidIndex) continue;
                minimumIndex = i < minimumIndex ? i : minimumIndex;
            }
            
            return minimumIndex;
        }

        public void Dispose()
        {
            if (dbContext != null)
            {
                dbContext.Dispose();
                dbContext = null;
            }
        }

        protected abstract int GetReportId();

        protected abstract bool IsTheReportPresent(int sectionNumber, MTUHAAgeCategory ageCategory);

        private void ResolveIncludedReports()
        {
            if (!monthAndYearChecked) IsReportMonthAndYearValid();
            if (this.error == ReportProcessingError.NO_ERROR)
            {
                includedReportsResolved = true;
                // 8. Extract the report(s)
                DetermineIncludedReports();
                if (this.error == ReportProcessingError.NO_ERROR)
                {
                    // 9. For each report extract indicators
                    int reportId = GetReportId();
                    if (reportId == INVALID_REPORT_ID)
                    {
                        error = ReportProcessingError.MTUHA_REPORT_IS_NOT_KNOWN_OR_CONFIGURED_PROPERLY;
                        this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                        return;
                    }

                    if (this.includedReports.Count < 1)
                    {
                        error = ReportProcessingError.INVALID_REPORT_INITIALIZATION;
                        this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                        return;
                    }

                    this.resolvedReports.Clear();
                    #region Determining Included Reports
                    foreach (string report in includedReports)
                    {
                        string reportSegment = report;
                        if (format == SMSReportingFormat.FLEXIBLE || format == SMSReportingFormat.HIGHLY_ADAPTIVE)
                        {
                            reportSegment = reportSegment.Replace('.', ',');
                        }

                        string[] parts = reportSegment.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                        if (parts.Length >= MINIMUM_NUMBER_OF_DATA_SEGMENTS)
                        {
                            //string ageCategoryText = parts[REPORT_DATA_AGE_CATEGORY_POSITION];
                            //var ageCategory = GetAgeCategoryFromText(ageCategoryText);
                            //if (ageCategory == MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY)
                            //{
                            //    error = ReportProcessingError.INVALID_REPORT_AGE_CATEGORY;
                            //    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                            //    break;
                            //}

                            //string sectionText = parts[REPORT_DATA_SECTION_CATEGORY_POSITIONS];
                            //int section = GetReportSectionFromText(sectionText);
                            //if (section == INVALID_REPORT_SECTION)
                            //{
                            //    error = ReportProcessingError.MTUHA_INVALID_REPORT_SECTION;
                            //    this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                            //    break;
                            //}

                            int section = INVALID_REPORT_SECTION;
                            var ageCategory = MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY;
                            DetermineAgeCategoryAndReportSection(report, out section, out ageCategory);
                            
                            if (ageCategory == MTUHAAgeCategory.UNKNOWN_AGE_CATEGORY)
                            {
                                error = ReportProcessingError.INVALID_REPORT_AGE_CATEGORY;
                                this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                                break;
                            }

                            if (section == INVALID_REPORT_SECTION)
                            {
                                error = ReportProcessingError.MTUHA_INVALID_REPORT_SECTION;
                                this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                                break;
                            }

                            StringBuilder sBuilder = new StringBuilder();
                            for (int i = REPORT_DATA_INDICATOR_VALUES_OFFSET; i < parts.Length; /*i++ <is now used inside the loop>*/)
                            {
                                sBuilder.Append(parts[i]);
                                if (++i != parts.Length) sBuilder.Append(',');
                            }
                            string parsingText = sBuilder.ToString();
                            List<int> indicators = GetIndicatorSectionValue(section, reportId, parsingText);
                            if (indicators == null) break;

                            // 10. Validate the report indicators against business rules
                            var resolvedReport = new ReportDataSegment()
                            {
                                AgeCategory = ageCategory,
                                SectionNumber = section,
                                Indicators = indicators
                            };
                            bool okToContinue = ValidateReportIndicators(resolvedReport);
                            
                            if (!okToContinue) break;   
                            //resolvedReport.Valid = okToContinue;
                            this.resolvedReports.Add(resolvedReport);
                        }
                        else
                        {
                            error = ReportProcessingError.FEW_REPORT_PARAMETERS;
                            this.reportingError = ReportingError.GetReportingError(error, PhoneNumber);
                            break;
                        }
                    }
                    #endregion
                }
            }
        }

        protected abstract void IdentifyInvolvedClassicalReportIds();

        public bool Save()
        {
            if (!validated) Validate();
            if (this.error == ReportProcessingError.NO_ERROR)
            {
                string sender;
                string senderPhoneNumber;
                if (inputingMethod == DataInputingMethod.SMS)
                {
                    sender = this.smsUser.Name;
                    senderPhoneNumber = this.smsUser.CellNum;
                }
                else
                {
                    sender = this.desktopUser.FirstName + " " + this.desktopUser.LastName;
                    senderPhoneNumber = this.desktopUser.PhoneNumber;
                }

                var inputMethod = DataInputMethod.GetDataInputMethod(inputingMethod);
                var metadata = new ANCFormMetadata
                {
                    InputMethodId = inputMethod.Id,
                    TimeReceived = DateTime.Now,
                    Sender = sender,
                    SenderPhoneNumber = senderPhoneNumber,
                    TextMessage = this.message
                };
                dbContext.ANCFormMetadatas.Add(metadata);

                foreach (var report in this.resolvedReports)
                {
                    if (this.reportingError.Type == ReportProcessingError.NO_ERROR)
                    {
                        bool alreadyPresent = IsTheReportPresent(report.SectionNumber, report.AgeCategory);
                        if (alreadyPresent) Update(report, metadata);
                        else Insert(report, metadata);
                    }
                    else return false;
                }

                if (this.saveInClassicFormat)
                {
                    return SaveInClassicalReportTemplates();
                }
                else return true;
            }
            else return false;
        }

        public bool SaveAndUploadToDHIS(string authorizationString)
        {
            if (this.error == ReportProcessingError.NO_ERROR)
            {
                var saved = Save();
                if (saved)
                {
                    var dhis = GenericTools.Models.OtherSystem.GetOtherSystem(OtherKnownSystem.DHIS2);
                    var facilityOtherDetails = Utilities.GetOtherSystemsFacilityDetails(dhis.Id, facilityCode);
                    string organizationUnit = facilityOtherDetails.FacilityCode;

                    bool atLeastOneErrorOccured = false;
                    foreach (int classicalReportId in this.involvedClassicalReportIds)
                    {
                        // Get the associated DHIS configurations
                        ReportDataSetLink reportDataSetLink = Utilities.GetReportDataSetLink(classicalReportId);
                        if (reportDataSetLink != null)
                        {
                            // Get the saved data.
                            DataTable dataTable = Utilities.GetFacilityReport(facilityCode,
                                classicalReportId, reportYear, reportMonth, reportYear, reportMonth);

                            // Now preparing for the upload
                            var dataValuesSet = new dataValueSet();
                            dataValuesSet.orgUnit = organizationUnit;
                            dataValuesSet.dataSet = reportDataSetLink.DataSetId;
                            dataValuesSet.period = string.Format("{0}{1}", reportYear, reportMonth);

                            List<dataValue> dataValues = new List<dataValue>();
                            // Going through the report indicators
                            foreach (DataRow row in dataTable.Rows)
                            {
                                string indicatorDescription = row[0].ToString();
                                string value = row[1].ToString();
                                // Now let's find its associated DHIS data element.
                                foreach (var elementLink in reportDataSetLink.ReportDataSetElementsLinks)
                                {
                                    if (elementLink.ReportIndicator.DataDescription == indicatorDescription)
                                    {
                                        var dataValue = new dataValue
                                        {
                                            dataElement = elementLink.DataElementId,
                                            value = value
                                        };
                                        dataValues.Add(dataValue);
                                        break;
                                    }
                                }
                            }

                            dataValuesSet.dataValue = dataValues.ToArray();
                            
                            // Preparing to send the data to DHIS server
                            string baseUrl = AppConfiguration.DHISBaseUrl;
                            var transferManager = new DataTransferManager(baseUrl, authorizationString);
                            var importSummary = transferManager.SendDataValues(dataValuesSet);

                            if (importSummary.status != importSummary.STATUS_SUCCESS) atLeastOneErrorOccured = true;
                        }                        
                    }

                    return (atLeastOneErrorOccured == false);
                }
            }

            return false;
        }

        protected abstract bool InsertComments(string comments);
        public bool SaveComments(string comments)
        {
            return InsertComments(comments);
        }

        protected static string GetSignText(string sign, string senderNum)
        {
            string text = String.Empty;
            int labelID = -1;
            switch (sign)
            {
                case "=":
                    labelID = EQUALS_LABEL;
                    break;

                case "!=":
                    labelID = NOT_EQUALS_LABEL;
                    break;

                case ">":
                    labelID = GREATER_THAN_LABEL;
                    break;

                case ">=":
                    labelID = GREATER_THAN_OR_EQUALS_LABEL;
                    break;

                case "<":
                    labelID = LESS_THAN_LABEL;
                    break;

                case "<=":
                    labelID = LESS_THAN_OR_EQUALS_LABEL;
                    break;
            }
            text = Utilities.GetTranslationText(senderNum, labelID);
            return text;
        }

        protected static int GetReportDataParameterCount(int classicalReportId)
        {
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"SELECT COUNT(DataPosition) AS [Count] FROM ReportDataLocations WHERE (RepID = @RepID)";

            DbParameter param = command.CreateParameter();
            param.ParameterName = "@RepID";
            param.DbType = DbType.Int32;
            param.Value = classicalReportId;
            command.Parameters.Add(param);

            string output = GenericDataAccess.ExecuteScalar(command);
            int count = int.Parse(output);
            return count;
        }

        protected static DataTable GetReportDataLocations(int classicalReportId)
        {
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"SELECT MeasureNo, RepID, DataDescription, DataPosition, DataType, ShortCode 
                FROM ReportDataLocations WHERE RepID=@RepID ORDER BY DataPosition ASC";
            DbParameter param = command.CreateParameter();
            param.ParameterName = "@RepID";
            param.DbType = DbType.Int32;
            param.Value = classicalReportId;
            command.Parameters.Add(param);

            DataTable output = GenericDataAccess.ExecuteSelectCommand(command);
            return output;
        }

        protected static DataTable GetReportBusinessRules(int classicalReportId)
        {
            DbCommand command = GenericDataAccess.CreateCommand();
            command.CommandType = CommandType.Text;
            command.CommandText = @"SELECT ReportBusinessRules.BRNo, ReportBusinessRules.RepID, 
                ReportBusinessRules.MeasurePosNo, ReportBusinessRules.RuleDescription, ReportBusinessRules.Action, 
                ReportBusinessRules.ComparedToOrderPos, ReportBusinessRules.ComparisonType, 
                ReportBusinessRules.Multicomparison, ComparisonType.ComparisonSign FROM ReportBusinessRules 
                INNER JOIN ComparisonType ON ReportBusinessRules.ComparisonType = ComparisonType.ComparisonType
                WHERE (ReportBusinessRules.RepID = @RepID)";

            DbParameter param = command.CreateParameter();
            param.ParameterName = "@RepID";
            param.DbType = DbType.Int32;
            param.Value = classicalReportId;
            command.Parameters.Add(param);

            DataTable dataTable = GenericDataAccess.ExecuteSelectCommand(command);
            return dataTable;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="senderNum"></param>
        /// <param name="classicalReportId"></param>
        /// <param name="reportData">The list of report data. These are facility code, month, year and indicators</param>
        /// <param name="e"></param>
        protected static void CheckAgainstBusinessRules(string senderNum, int classicalReportId,
            List<string> reportData, out ReportingError e)
        {
            // Up to this point there should be no errors
            // so we're going to ignore any previous generated error
            //bool hasError = false;
            e = ReportingError.GetReportingError(ReportingError.NO_ERROR_LABEL, senderNum);

            #region Checking Report Parameter Count
            int requiredParameterCount = GetReportDataParameterCount(classicalReportId);
            if (requiredParameterCount != reportData.Count)
            {
                if (requiredParameterCount > reportData.Count)
                {
                    e = ReportingError.GetReportingError(ReportingError.FEW_REPORT_PARAMETERS_LABEL, senderNum);
                }
                else
                {
                    e = ReportingError.GetReportingError(ReportingError.MANY_REPORT_PARAMETERS_LABEL, senderNum);
                }

                //hasError = true;
                return;
            }
            #endregion

            //E.g. ANC Jan 2013: Taarifa yako imepokolewa...
            //e.errorText = String.Format("{0} {1} {2}: {3}", this.reportKeyword, Utilities.GetMonthText(this.period, true),
            //    this.year, this.errorText);

            DataTable reportDataLocation = GetReportDataLocations(classicalReportId);

            //int offset = 4; //this is because position 1,2,3 are for code, period and year. Don't forget zero-based list/array.
            DataTable dataTable = GetReportBusinessRules(classicalReportId);
            for (int i = 0; i < dataTable.Rows.Count; i++)
            {
                DataRow row = dataTable.Rows[i];
                int measurePosNo = int.Parse(row["MeasurePosNo"].ToString());
                string comparedToOrderPos = row["ComparedToOrderPos"].ToString();
                bool multicomparison = bool.Parse(row["Multicomparison"].ToString());
                string comparisonSign = row["ComparisonSign"].ToString();

                decimal value1 = 0.0M;
                decimal value2 = 0.0M;

                if (multicomparison)
                {
                    #region Dealing with Multicomparison
                    ArithmeticOperation operation = ArithmeticOperation.ADD;
                    //value1 = data[measurePosNo - offset];
                    value1 = decimal.Parse(reportData[measurePosNo - 1]);
                    StringBuilder sBuilder = new StringBuilder();
                    sBuilder.Append("(");
                    string[] parts = comparedToOrderPos.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string part in parts)
                    {
                        try
                        {
                            int position = int.Parse(part);
                            //int value = data[position - offset];
                            decimal value = decimal.Parse(reportData[position - 1]);
                            string shortCode = reportDataLocation.Rows[position - 1]["ShortCode"].ToString();
                            sBuilder.Append(shortCode);
                            switch (operation)
                            {
                                case ArithmeticOperation.ADD:
                                    value2 += value;
                                    break;

                                case ArithmeticOperation.SUBTRACT:
                                    value2 -= value;
                                    break;

                                case ArithmeticOperation.MULTIPLY:
                                    value2 *= value;
                                    break;

                                case ArithmeticOperation.DIVIDE:
                                    value2 /= value;
                                    break;
                            }
                        }
                        catch
                        {
                            switch (part)
                            {
                                case "+":
                                    operation = ArithmeticOperation.ADD;
                                    sBuilder.Append("+");
                                    break;

                                case "-":
                                    operation = ArithmeticOperation.SUBTRACT;
                                    sBuilder.Append("-");
                                    break;

                                case "*":
                                    operation = ArithmeticOperation.MULTIPLY;
                                    sBuilder.Append("*");
                                    break;

                                case "/":
                                    operation = ArithmeticOperation.DIVIDE;
                                    sBuilder.Append("/");
                                    break;
                            }
                        }
                    }
                    sBuilder.Append(")");
                    bool okToContinue = Utilities.Compare(value1, comparisonSign, value2);
                    if (!okToContinue)
                    {
                        //hasError = true;
                        e = ReportingError.GetReportingError(ReportingError.BUSINESS_RULES_FAILURE_LABEL, senderNum);

                        string sValue1 = reportDataLocation.Rows[measurePosNo - 1]["ShortCode"].ToString();
                        string sValue2 = sBuilder.ToString();

                        string msg = Utilities.GetTranslationText(senderNum, COMPARISON_LABEL);
                        string signText = GetSignText(comparisonSign, senderNum);
                        e.ErrorText = String.Format(msg, sValue1, signText, sValue2);




                        //E.g. ANC Jan 2013: Kosa kwenye taarifa...
                        // TODO: Refine reply message for each report section
                        //e.ErrorText = String.Format("{0} {1} {2}: {3}", this.reportKeyword, Utilities.GetMonthText(this.period, true),
                        //    this.year, e.ErrorText);     

                        e = ReportingError.GetReportingError(e.Code, senderNum);
                        e.ErrorText = String.Format(msg, sValue1, signText, sValue2);
                        //if (ReportBussinessRulesFailed != null) ReportBussinessRulesFailed(this, e);
                        return;
                    }
                    #endregion
                }
                else
                {
                    #region Dealing with Simple Comparison
                    bool value2IsScalar = false;
                    int comparePosNo = 0;
                    //value1 = data[measurePosNo - offset];
                    value1 = decimal.Parse(reportData[measurePosNo - 1]);
                    if (comparedToOrderPos.Contains("["))
                    {
                        value2IsScalar = true;
                        string value = comparedToOrderPos.Substring(1, comparedToOrderPos.Length - 2);
                        //value2 = Int32.Parse(value);
                        value2 = Decimal.Parse(value);
                    }
                    else
                    {
                        comparePosNo = int.Parse(comparedToOrderPos);
                        //value2 = data[comparePosNo - offset];    
                        value2 = decimal.Parse(reportData[comparePosNo - 1]);
                    }
                    bool okToContinue = Utilities.Compare(value1, comparisonSign, value2);
                    if (!okToContinue)
                    {
                        //hasError = true;
                        e = ReportingError.GetReportingError(ReportingError.BUSINESS_RULES_FAILURE_LABEL, senderNum);

                        string sValue1 = reportDataLocation.Rows[measurePosNo - 1]["ShortCode"].ToString();
                        string sValue2 = String.Empty;
                        if (value2IsScalar) sValue2 = value2.ToString();
                        else sValue2 = reportDataLocation.Rows[comparePosNo - 1]["ShortCode"].ToString();

                        string msg = Utilities.GetTranslationText(senderNum, COMPARISON_LABEL);
                        string signText = GetSignText(comparisonSign, senderNum);
                        e.ErrorText = String.Format(msg, sValue1, signText, sValue2);
                        //E.g. ANC Jan 2013: Kosa kwenye taarifa...
                        // TODO: Refine reply message for each report section
                        //this.errorText = String.Format("{0} {1} {2}: {3}", this.reportKeyword, Utilities.GetMonthText(this.period, true),
                        //    this.year, this.errorText);

                        e = ReportingError.GetReportingError(e.Code, senderNum);
                        e.ErrorText = String.Format(msg, sValue1, signText, sValue2);
                        //if (ReportBussinessRulesFailed != null) ReportBussinessRulesFailed(this, e);
                        return;
                    }
                    #endregion
                }
            }

            return;
        }

        /// <summary>
        /// This saves the report in classical format.
        /// Classical format makes it easy to manipulate and enforce business rules.
        /// New forms can be modified/adjusted to classical format so that they can take
        /// the advantage of business rule functions established.
        /// </summary>
        protected void SaveReportInClassicalFormat(int classicalReportId, List<string> reportData)
        {
            var results = from r in dbContext.ReceivedSMS
                          where r.FacCode == this.facilityCode &&
                          r.Period == this.reportMonth &&
                          r.Year == this.reportYear &&
                          r.RepID == classicalReportId
                          select r;

            foreach (var r in results) dbContext.ReceivedSMS.Remove(r);
            dbContext.SaveChanges();

            var i = new ReceivedSM
            {
                SenderNum = this.phoneNumberOrUsername,
                TxtDetails = this.message,
                RepID = classicalReportId,
                Period = this.reportMonth,
                Year = this.reportYear,
                ReportDate = DateTime.Parse(string.Format("{0}-{1}-01", this.reportYear, this.reportMonth)),
                FacCode = this.facilityCode,
                DateReceived = DateTime.Now,
                Processed = true
            };
            dbContext.ReceivedSMS.Add(i);
            dbContext.SaveChanges();
            int insertedId = i.ReceivedSMSNo;

            for (int k = 0; k < reportData.Count; k++)
            {
                var d = reportData[k];
                var data = new ReportData
                {
                    RepID = classicalReportId,
                    ReceivedSMSNo = insertedId,
                    DataPosition = k + 1,
                    Data = d
                };
                dbContext.ReportDatas.Add(data);
            }
            dbContext.SaveChanges();
            
            // Is this report involved in any sort of auto-merging
            var merges = from m in dbContext.MergingReports
                         join r in dbContext.Reports on m.DestinationReportId equals r.RepID
                         where r.UsedAsView == true
                         && m.DestinationReportId != m.SourceReportId
                         && m.SourceReportId == classicalReportId
                         select m;

            // This query does not focus on reports used as view only, it aims to capture all reports
            // with auto-merging.
            //var merges = from m in dbContext.MergingReports
            //             join r in dbContext.Reports on m.DestinationReportId equals r.RepID
            //             where m.DestinationReportId != m.SourceReportId
            //             && m.SourceReportId == classicalReportId
            //             select m;

            foreach (var merge in merges)
            {
                var status = Merging.MergeFacilityReports(merge.MergeId.Value, true, 
                    this.facilityCode, this.reportYear, this.reportMonth, this.reportYear, 
                    this.reportMonth, this.phoneNumberOrUsername);
                if (!status.Merged && status.TotalSubmitted == 1 && status.TotalRejected > 0)
                {
                    string errorLabel = string.Format("Data Merging and Business Rules Inconsistency. " +
                        "Reporting Text:{0}", this.message);
                    Exception ex = new Exception(errorLabel);
                    Utilities.LogError(ex, null);
                }
            }
        }
    }
}
